﻿using Endos.Wodo.Domain;
using Endos.Wodo.Persistence;
using System;
using System.Linq;

namespace Endos.Wodo.Business
{
    internal class CharacterService : Service, ICharacterService
    {
        private const int attributeFactor = 5;
        private const int skillFactor = 3;

        private readonly INotifierContext<ICharacterNotifier> notifierContext;
        private Character character;
        private int? userId;
        private int? characterId;



        public CharacterService(IDataContext context, INotifierContext<ICharacterNotifier> notifierContext)
            : base(context)
        {
            this.notifierContext = notifierContext;
        }

        public Character Character
        {
            get 
            {
                if (character == null)
                {
                    if (characterId.HasValue)
                    {
                        LoadCharacter();
                    }
                    else
                    {
                        character = new Character()
                        {
                            Strength = 1,
                            Dexterity = 1,
                            Stamina = 1,
                            Presence = 1,
                            Manipulation = 1,
                            Composure = 1,
                            Intelligence = 1,
                            Wits = 1,
                            Resolve = 1,
                            ModifierVitality = 5,
                            ModifierInitiative = 5,
                            ModifierSize = 5,
                            Morality = MoralityType.Seven,
                            IsDraft = true,
                            User = Context.CreateQuery<User>().Queryable.Single(e => e.Id == userId.Value)
                        };
                        Context.Insert(character);
                    } 
                }

                return character;
            }
        }

        private void CheckAuthorization(UserAuthorizationType userAuthorizationType)
        {
            var throwException = false;

            switch (userAuthorizationType)
            {
                case UserAuthorizationType.Player:
                    throwException = character.Chronicle.User.Id != userId.Value && character.User.Id != userId.Value;
                    break;
                case UserAuthorizationType.PlayerWithDraft:
                    throwException = (character.Chronicle.User.Id != userId.Value && character.User.Id != userId.Value) 
                        || (character.User.Id == userId.Value && !character.IsDraft);
                    break;
                case UserAuthorizationType.Storyteller:
                    throwException = character.Chronicle.User.Id != userId.Value;
                    break;
            }

            if (throwException)
            {
                throw new Exception("Não autorizado.");
            }
        }

        private void CalculateCostExperience(int factor, int oldValue, int newValue)
        {
            if (newValue > oldValue && !character.IsDraft && character.User.Id == userId.Value)
            {
                for (int i = oldValue + 1; i <= newValue; i++)
                {
                    character.Experience -= i * factor;
                }
            }
        }

        private void CalculateDamage(int vitality, int value, int damage, Action<int> property, Action action = null)
        {
            if (action != null && vitality <= damage)
            {
                action();
            }
            else if (vitality < damage + value)
            {
                property(vitality);
            }
            else
            {
                property(damage + value);
            }
        }

        private void LoadCharacter()
        {
            character = Context.CreateQuery<Character>().Queryable.Single(e => e.Id == characterId.Value);
        }

        private void UpdateCharacter()
        {
            Context.Update(character);
        }

        public void Configure(int userId, int? characterId = null)
        {
            this.userId = userId;
            this.characterId = characterId;
        }

        public void Define(Chronicle chronicle)
        {
            LoadCharacter();
            CheckAuthorization(UserAuthorizationType.PlayerWithDraft);
            character.Chronicle = chronicle;
            if (character.TotalExperience == 0)
            {
                character.Experience = Context.CreateQuery<Chronicle>().Queryable.Where(e => e.Id == chronicle.Id)
                    .Select(e => e.Experience).Single();
                character.TotalExperience = character.Experience;
            }
            UpdateCharacter();
            var notifier = notifierContext.All;
            notifier.CharacterId = characterId.Value;
            notifier.Define(chronicle);
        }

        public void Define(Armor armor)
        {
            LoadCharacter();
            CheckAuthorization(UserAuthorizationType.PlayerWithDraft);
            character.Armor = armor;
            UpdateCharacter();
            var notifier = notifierContext.All;
            notifier.CharacterId = characterId.Value;
            notifier.Define(armor);
        }

        public void Define(VirtueType virtue)
        {
            LoadCharacter();
            CheckAuthorization(UserAuthorizationType.PlayerWithDraft);
            character.Virtue = virtue;
            UpdateCharacter();
            var notifier = notifierContext.All;
            notifier.CharacterId = characterId.Value;
            notifier.Define(virtue);
        }

        public void Define(ViceType vice)
        {
            LoadCharacter();
            CheckAuthorization(UserAuthorizationType.PlayerWithDraft);
            character.Vice = vice;
            UpdateCharacter();
            var notifier = notifierContext.All;
            notifier.CharacterId = characterId.Value;
            notifier.Define(vice);
        }

        public void Define(MoralityType morality)
        {
            LoadCharacter();
            CheckAuthorization(UserAuthorizationType.Player);
            CalculateCostExperience(3, (int)character.Morality, (int)morality);
            character.Morality = morality;
            foreach (var item in Context.CreateQuery<CharacterDisturbance>().Queryable
                .Where(e => e.Character.Id == character.Id && e.Morality != null && e.Morality <= morality))
            {
                Context.Delete(item);
            }
            UpdateCharacter();
            var notifier = notifierContext.All;
            notifier.CharacterId = characterId.Value;
            notifier.Define(morality);
        }

        public void Define(FieldType field, int value)
        {
            LoadCharacter();           
            switch (field)
            {
                case FieldType.Strength:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(attributeFactor, character.Strength, value);
                    character.Strength = value;
                    break;
                case FieldType.Dexterity:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(attributeFactor, character.Dexterity, value);
                    character.Dexterity = value;
                    break;
                case FieldType.Stamina:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(attributeFactor, character.Stamina, value);
                    character.Stamina = value;
                    break;
                case FieldType.Presence:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(attributeFactor, character.Presence, value);
                    character.Presence = value;
                    break;
                case FieldType.Manipulation:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(attributeFactor, character.Manipulation, value);
                    character.Manipulation = value;
                    break;
                case FieldType.Composure:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(attributeFactor, character.Composure, value);
                    character.Composure = value;
                    break;
                case FieldType.Intelligence:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(attributeFactor, character.Intelligence, value);
                    character.Intelligence = value;
                    break;
                case FieldType.Wits:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(attributeFactor, character.Wits, value);
                    character.Wits = value;
                    break;
                case FieldType.Resolve:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(attributeFactor, character.Resolve, value);
                    character.Resolve = value;
                    break;
                case FieldType.Athletics:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(skillFactor, character.Athletics, value);
                    character.Athletics = value;
                    break;
                case FieldType.Brawl:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(skillFactor, character.Brawl, value);
                    character.Brawl = value;
                    break;
                case FieldType.Drive:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(skillFactor, character.Drive, value);
                    character.Drive = value;
                    break;
                case FieldType.Firearms:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(skillFactor, character.Firearms, value);
                    character.Firearms = value;
                    break;
                case FieldType.Larceny:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(skillFactor, character.Larceny, value);
                    character.Larceny = value;
                    break;
                case FieldType.Stealth:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(skillFactor, character.Stealth, value);
                    character.Stealth = value;
                    break;
                case FieldType.Survival:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(skillFactor, character.Survival, value);
                    character.Survival = value;
                    break;
                case FieldType.Weaponry:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(skillFactor, character.Weaponry, value);
                    character.Weaponry = value;
                    break;
                case FieldType.AnimalKen:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(skillFactor, character.AnimalKen, value);
                    character.AnimalKen = value;
                    break;
                case FieldType.Empathy:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(skillFactor, character.Empathy, value);
                    character.Empathy = value;
                    break;
                case FieldType.Expression:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(skillFactor, character.Expression, value);
                    character.Expression = value;
                    break;
                case FieldType.Intimidation:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(skillFactor, character.Intimidation, value);
                    character.Intimidation = value;
                    break;
                case FieldType.Persuasion:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(skillFactor, character.Persuasion, value);
                    character.Persuasion = value;
                    break;
                case FieldType.Socialize:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(skillFactor, character.Socialize, value);
                    character.Socialize = value;
                    break;
                case FieldType.Streetwise:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(skillFactor, character.Streetwise, value);
                    character.Streetwise = value;
                    break;
                case FieldType.Subterfuge:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(skillFactor, character.Subterfuge, value);
                    character.Subterfuge = value;
                    break;
                case FieldType.Academics:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(skillFactor, character.Academics, value);
                    character.Academics = value;
                    break;
                case FieldType.Crafts:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(skillFactor, character.Crafts, value);
                    character.Crafts = value;
                    break;
                case FieldType.Computer:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(skillFactor, character.Computer, value);
                    character.Computer = value;
                    break;
                case FieldType.Investigation:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(skillFactor, character.Investigation, value);
                    character.Investigation = value;
                    break;
                case FieldType.Medicine:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(skillFactor, character.Medicine, value);
                    character.Medicine = value;
                    break;
                case FieldType.Occult:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(skillFactor, character.Occult, value);
                    character.Occult = value;
                    break;
                case FieldType.Politics:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(skillFactor, character.Politics, value);
                    character.Politics = value;
                    break;
                case FieldType.Science:
                    CheckAuthorization(UserAuthorizationType.Player);
                    CalculateCostExperience(skillFactor, character.Science, value);
                    character.Science = value;
                    break;
                case FieldType.Experience:
                    CheckAuthorization(UserAuthorizationType.Storyteller);
                    character.Experience += value;
                    character.TotalExperience += value;
                    break;
                case FieldType.WillpowerSpent:
                    CheckAuthorization(UserAuthorizationType.Storyteller);
                    character.WillpowerSpent += value;
                    break;
                case FieldType.ModifierVitality:
                    CheckAuthorization(UserAuthorizationType.Storyteller);
                    character.ModifierVitality += value;
                    break;
                case FieldType.ModifierInitiative:
                    CheckAuthorization(UserAuthorizationType.Storyteller);
                    character.ModifierInitiative += value;
                    break;
                case FieldType.ModifierSize:
                    CheckAuthorization(UserAuthorizationType.Storyteller);
                    character.ModifierSize += value;
                    break;
                case FieldType.ModifierDefense:
                    CheckAuthorization(UserAuthorizationType.Storyteller);
                    character.ModifierDefense += value;
                    break;
                case FieldType.ModifierWillpower:
                    CheckAuthorization(UserAuthorizationType.Storyteller);
                    character.ModifierWillpower += value;
                    break;
                case FieldType.BashingDamage:
                    CheckAuthorization(UserAuthorizationType.Storyteller);
                    var vitalityBashingDamage = character.Vitality();
                    CalculateDamage(vitalityBashingDamage, value, character.BashingDamage, d => character.BashingDamage = d,
                    () => 
                    {
                        CalculateDamage(vitalityBashingDamage, value, character.LethalDamage, d => character.LethalDamage = d,
                            () =>
                            {
                                CalculateDamage(vitalityBashingDamage, value, character.AggravatedDamage, d => character.AggravatedDamage = d);
                            });
                    });
                    break;
                case FieldType.LethalDamage:
                    CheckAuthorization(UserAuthorizationType.Storyteller);
                    var vitalityLethalDamage = character.Vitality();
                    CalculateDamage(vitalityLethalDamage, value, character.LethalDamage, d => character.LethalDamage = d,
                        () =>
                        {
                            CalculateDamage(vitalityLethalDamage, value, character.AggravatedDamage, d => character.AggravatedDamage = d);
                        });
                    break;
                case FieldType.AggravatedDamage:
                    CheckAuthorization(UserAuthorizationType.Storyteller);
                    CalculateDamage(character.Vitality(), value, character.AggravatedDamage, d => character.AggravatedDamage = d);
                    break;
            }
            UpdateCharacter();
            var notifier = notifierContext.All;
            notifier.CharacterId = characterId.Value;
            notifier.Define(field, value);
        }

        public void Increase(Merit merit)
        {
            throw new System.NotImplementedException();
        }

        public void Decrease(Merit merit)
        {
            throw new System.NotImplementedException();
        }

        public void Add(Disturbance disturbance, MoralityType? morality = null)
        {
            throw new System.NotImplementedException();
        }

        public void Add(Weapon weapon)
        {
            throw new System.NotImplementedException();
        }

        public void Add(Equipment equipment)
        {
            throw new System.NotImplementedException();
        }

        public void Add(Specialization specialization)
        {
            throw new System.NotImplementedException();
        }

        public void Add(Flaw flaw)
        {
            throw new System.NotImplementedException();
        }

        public void Remove(Disturbance disturbance)
        {
            throw new System.NotImplementedException();
        }

        public void Remove(Weapon weapon)
        {
            throw new System.NotImplementedException();
        }

        public void Remove(Equipment equipment)
        {
            throw new System.NotImplementedException();
        }

        public void Remove(Specialization specialization)
        {
            throw new System.NotImplementedException();
        }

        public void Remove(Flaw flaw)
        {
            throw new System.NotImplementedException();
        }

        private enum UserAuthorizationType 
        {
            Player,
            PlayerWithDraft,
            Storyteller
        }
    }
}
